﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Moon.Sql
{
    public class DynamicParameters : IDynamicParameters
    {
        private readonly Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();

        public void Add(string name, object value)
        {
            parameters[Clean(name)] = new ParamInfo
            {
                Name = name,
                Value = value,
                ParameterDirection = ParameterDirection.Input
            };
        }

        public void Add(DynamicParameters parameters)
        {
            foreach (KeyValuePair<string, ParamInfo> item in parameters.Get())
            {
                Add(item.Key, item.Value.Value);
            }
        }

        public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size)
        {
            parameters[Clean(name)] = new ParamInfo
            {
                Name = name,
                Value = value,
                ParameterDirection = direction ?? ParameterDirection.Input,
                DbType = dbType,
                Size = size
            };
        }

        private void AddParameters(IDbCommand cmd, ParamInfo paramInfo)
        {
            object value = paramInfo.Value;
            int count = 0;
            DbType dbType = 0;
            string namePrefix = "@";
            string cmdText = cmd.CommandText;
            if (cmdText.Contains($":{paramInfo.Name}"))
            {
                namePrefix = ":";
            }
            else if (cmdText.Contains($"?{paramInfo.Name}"))
            {
                namePrefix = "?";
            }
            var builder = new StringBuilder("(");
            foreach (var item in (Array)value)
            {
                if (++count == 1)
                {
                    //first
                    if (item == null)
                    {
                        throw new ArgumentNullException("Value cannot be null");
                    }
                    dbType = DataMapper.LoadDbType(item.GetType());
                }
                string nextName = paramInfo.Name + count.ToString();
                IDbDataParameter param = cmd.CreateParameter();
                param.ParameterName = nextName;
                param.Value = DataMapper.LoadParameterValue(item);
                param.Direction = paramInfo.ParameterDirection;
                param.DbType = dbType;
                cmd.Parameters.Add(param);
                builder.Append(namePrefix).Append(nextName).Append(",");
            }
            builder.Remove(builder.ToString().Length - 1, 1);
            builder.Append(")");
            cmd.CommandText = cmdText.Replace(namePrefix + paramInfo.Name, builder.ToString());
        }

        public void AddParameters(IDbCommand cmd)
        {
            foreach (var item in parameters.Values)
            {
                //in param array
                if (item.Value is Array)
                {
                    AddParameters(cmd, item);
                    continue;
                }
                //in param list #2022.10.31
                if (item.Value != null && (item.Value.GetType().FullName.Contains("List") || item.Value.GetType().FullName.Contains("Enumerable")))
                {
                    var array = new List<string>();
                    foreach (var itemValue in item.Value as IEnumerable)
                    {
                        array.Add(itemValue?.ToString());
                    }
                    item.Value = array.ToArray();
                    AddParameters(cmd, item);
                    continue;
                }
                IDbDataParameter param = null;
                bool isAdd = false;
                if (!cmd.Parameters.Contains(item.Name))
                {
                    param = cmd.CreateParameter();
                    isAdd = true;
                }
                else
                {
                    param = cmd.Parameters[item.Name] as IDbDataParameter;
                }
                param.ParameterName = item.Name;
                param.Direction = item.ParameterDirection;

                var dbType = item.DbType;
                var val = item.Value;

                param.Value = DataMapper.LoadParameterValue(val);
                if (dbType == null && val != null)
                {
                    dbType = DataMapper.LoadDbType(val.GetType());
                }
                if (item.Size.HasValue)
                {
                    param.Size = item.Size.Value;
                }
                else
                {
                    var s = val as string;
                    if (s?.Length <= 4000)
                    {
                        //#2022.02.24 20:20
                        param.Size = Encoding.GetEncoding("GB2312").GetByteCount(s);
                    }
                }
                param.DbType = !dbType.HasValue ? DbType.String : dbType.Value;
                item.AttachedParam = param;
                if (isAdd) cmd.Parameters.Add(param);
            }
        }

        private string Clean(string name)
        {
            if (!string.IsNullOrEmpty(name))
            {
                switch (name[0])
                {
                    case '@':
                    case ':':
                    case '?':
                        return name.Substring(1);
                }
            }
            return name;
        }

        public object Get(string name)
        {
            parameters.TryGetValue(name, out ParamInfo paramInfo);
            var attachedParam = paramInfo.AttachedParam;
            object val = attachedParam == null ? paramInfo.Value : attachedParam.Value;
            return val;
        }

        public bool Any()
        {
            return parameters.Keys.Any();
        }

        public Dictionary<string, ParamInfo> Get()
        {
            return parameters;
        }

        public void Clear()
        {
            parameters.Clear();
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            foreach (KeyValuePair<string, ParamInfo> item in parameters)
            {
                builder.Append(item.Key).Append("=").Append((item.Value.Value == null || item.Value.Value is DBNull) ? "NULL" : item.Value.Value).Append(",");
            }
            return builder.Length == 0 ? "" : builder.ToString(0, builder.Length - 1);
        }
    }

    public class ParamInfo
    {
        public string Name { get; set; }
        public object Value { get; set; }
        public ParameterDirection ParameterDirection { get; set; }
        public IDbDataParameter AttachedParam { get; set; }
        public DbType? DbType { get; set; }
        public int? Size { get; set; }
        public byte? Precision { get; set; }
        public byte? Scale { get; set; }
    }
}
